#include "preHeader.h"
#include "CreatureLooterMgr.h"
#include "Map/MapInstance.h"

#define ActiveLooterDistMax (30.f*30.f)

CreatureLooterMgr::CreatureLooterMgr(Creature* pCreature)
: m_pCreature(pCreature)
{
}

CreatureLooterMgr::~CreatureLooterMgr()
{
}

void CreatureLooterMgr::AddScore(Player* pPlayer, uint64 score)
{
	auto itr = m_allLooters.find(pPlayer->GetGuid());
	if (itr != m_allLooters.end()) {
		itr->second.totalScore += score;
	} else {
		m_allLooters.emplace(pPlayer->GetGuid(), Looter{score});
	}
}

Player* CreatureLooterMgr::CalcFinalLooter() const
{
	struct ActiveLooter {
		Player* pPlayer;
		uint64 totalScore;
	};
	std::vector<ActiveLooter> activeLooters;
	activeLooters.reserve(m_allLooters.size());

	auto pMapInstance = m_pCreature->GetMapInstance();
	for (auto&[guid, looter] : m_allLooters) {
		auto pPlayer = pMapInstance->GetPlayer(guid);
		if (pPlayer == NULL) {
			continue;
		}
		if (pPlayer->GetDistanceSq(m_pCreature) > ActiveLooterDistMax) {
			continue;
		}
		activeLooters.push_back({pPlayer, looter.totalScore});
	}
	if (activeLooters.empty()) {
		return NULL;
	}

	auto sltIdx = RandomByWeightValue<s64>(activeLooters.data(),
		activeLooters.size(), [](const ActiveLooter& looter) {
		return (s64)looter.totalScore;
	});
	return activeLooters[sltIdx].pPlayer;
}

RankLooterMgr::RankLooterMgr(size_t maxRank)
: m_maxRank(maxRank)
, m_rankTree(std::move(BetterLooter))
{
}

RankLooterMgr::~RankLooterMgr()
{
}

void RankLooterMgr::AddScore(ObjGUID playerGuid,
	const std::string_view& playerName, uint64 score)
{
	auto itr = m_rankTree.find(playerGuid);
	if (itr != m_rankTree.end()) {
		m_rankTree.Dirty(itr, [=](Looter& looter) {
			looter.totalScore += score;
			return -1;
		});
	} else {
		itr = m_rankTree.Add(playerGuid,
			Looter{std::string(playerName), score}).first;
	}
	auto rank = m_rankTree.GetRank(itr);
	if (rank <= m_maxRank) {
		m_rankCachePacket.Clear();
		auto rankNum = std::min(m_maxRank, m_rankTree.size());
		m_rankCachePacket << (uint32)rankNum;
		auto itr = m_rankTree.begin();
		for (size_t i = 0; i < rankNum; ++i, ++itr) {
			auto&&[playerGuid, looter] = std::tie(itr->key, itr->v);
			m_rankCachePacket << playerGuid
				<< looter.playerName << looter.totalScore;
		}
	}
}

const NetBuffer& RankLooterMgr::GetRankCachePacket() const
{
	return m_rankCachePacket;
}

std::pair<size_t, uint64> RankLooterMgr::GetRank(ObjGUID playerGuid) const
{
	auto itr = m_rankTree.find(playerGuid);
	if (itr == m_rankTree.end()) {
		return {0, 0};
	}
	return {m_rankTree.GetRank(itr), itr->v.totalScore};
}

bool RankLooterMgr::BetterLooter(const Looter& looter1, const Looter& looter2)
{
	return looter1.totalScore > looter2.totalScore;
}

TeamRankLooterMgr::TeamRankLooterMgr(size_t maxRank)
: m_maxRank(maxRank)
, m_rankTree(std::move(BetterRankTeam))
{
}

TeamRankLooterMgr::~TeamRankLooterMgr()
{
}

void TeamRankLooterMgr::AddScore(
	ObjGUID playerGuid, const std::string_view& playerName,
	uint32 teamId, const std::string_view& teamName, uint64 score)
{
	auto itr = m_allLooters.find(playerGuid);
	if (itr != m_allLooters.end()) {
		itr->second.totalScore += score;
	} else {
		itr = m_allLooters.emplace(playerGuid,
			Looter{std::string(playerName), score, teamId}).first;
	}
	auto& looter = itr->second;
	DBGASSERT(looter.teamId == teamId);

	RankGUID rankGuid;
	if (looter.teamId != 0) {
		rankGuid = RankGUID4Team(playerGuid.SID, looter.teamId);
	} else {
		rankGuid = RankGUID4Player(playerGuid);
	}
	auto itrx = m_rankTree.find(rankGuid);
	if (itrx != m_rankTree.end()) {
		m_rankTree.Dirty(itrx, [=, &looter](RankTeam& rankTeam) {
			rankTeam.teamMembers.emplace(playerGuid, &looter);
			rankTeam.totalScore += score;
			return -1;
		});
	} else {
		itrx = m_rankTree.Add(rankGuid, {looter.totalScore,
			std::string(teamName), {{playerGuid, &looter}}}).first;
	}
	if (m_rankTree.GetRank(itrx) <= m_maxRank) {
		RebuildRankCachePacket();
	}
}

void TeamRankLooterMgr::ChangeTeam(ObjGUID playerGuid,
	uint32 teamId, const std::string_view& teamName)
{
	auto itr = m_allLooters.find(playerGuid);
	if (itr == m_allLooters.end()) {
		return;
	}
	auto& looter = itr->second;
	if (looter.teamId == teamId) {
		return;
	}

	RankGUID rankGuid;
	if (looter.teamId != 0) {
		rankGuid = RankGUID4Team(playerGuid.SID, looter.teamId);
	} else {
		rankGuid = RankGUID4Player(playerGuid);
	}
	bool isPacketDirty = false;
	auto itrx = m_rankTree.find(rankGuid);
	if (itrx != m_rankTree.end()) {
		bool hasTeamMember = true;
		isPacketDirty = m_rankTree.GetRank(itrx) <= m_maxRank;
		m_rankTree.Dirty(itrx, [&, playerGuid](RankTeam& rankTeam) {
			if (rankTeam.teamMembers.erase(playerGuid) != 1) {
				return 0;
			}
			if (!rankTeam.teamMembers.empty()) {
				rankTeam.totalScore -= looter.totalScore;
				return 1;
			}
			hasTeamMember = false;
			return 0;
		});
		if (!hasTeamMember) {
			m_rankTree.Remove(rankGuid);
		}
	}

	looter.teamId = teamId;
	if (teamId != 0) {
		rankGuid = RankGUID4Team(playerGuid.SID, teamId);
	} else {
		rankGuid = RankGUID4Player(playerGuid);
	}
	if (itrx = m_rankTree.find(rankGuid), itrx != m_rankTree.end()) {
		m_rankTree.Dirty(itrx, [&, playerGuid](RankTeam& rankTeam) {
			if (!rankTeam.teamMembers.emplace(playerGuid, &looter).second) {
				return 0;
			}
			rankTeam.totalScore += looter.totalScore;
			return -1;
		});
	} else {
		itrx = m_rankTree.Add(rankGuid, {looter.totalScore,
			std::string(teamName), {{playerGuid, &looter}}}).first;
	}
	if (isPacketDirty || m_rankTree.GetRank(itrx) <= m_maxRank) {
		RebuildRankCachePacket();
	}
}

void TeamRankLooterMgr::RebuildRankCachePacket()
{
	m_rankCachePacket.Clear();
	auto rankNum = std::min(m_maxRank, m_rankTree.size());
	m_rankCachePacket << (uint32)rankNum;
	auto itr = m_rankTree.begin();
	for (size_t i = 0; i < rankNum; ++i, ++itr) {
		auto&&[rankGuid, rankTeam] = std::tie(itr->key, itr->v);
		m_rankCachePacket << (uint8)rankTeam.teamMembers.size();
		for (auto&[playerGuid, pLooter] : rankTeam.teamMembers) {
			m_rankCachePacket << playerGuid
				<< pLooter->playerName << pLooter->totalScore;
		}
		m_rankCachePacket << rankTeam.teamName << rankTeam.totalScore;
	}
}

const NetBuffer& TeamRankLooterMgr::GetRankCachePacket() const
{
	return m_rankCachePacket;
}

std::pair<size_t, uint64> TeamRankLooterMgr::GetRank(ObjGUID playerGuid) const
{
	auto itr = m_allLooters.find(playerGuid);
	if (itr == m_allLooters.end()) {
		return {0, 0};
	}
	auto& looter = itr->second;
	RankGUID rankGuid;
	if (looter.teamId != 0) {
		rankGuid = RankGUID4Team(playerGuid.SID, looter.teamId);
	} else {
		rankGuid = RankGUID4Player(playerGuid);
	}
	auto itrx = m_rankTree.find(rankGuid);
	if (itrx == m_rankTree.end()) {
		return {0, looter.totalScore};
	}
	return {m_rankTree.GetRank(itrx), itrx->v.totalScore};
}

auto TeamRankLooterMgr::RankGUID4Team(uint16 gsId, uint32 teamId) -> RankGUID
{
	return MakeGuid(gsId, 0, teamId).objGUID;
}

auto TeamRankLooterMgr::RankGUID4Player(ObjGUID playerGuid) -> RankGUID
{
	return playerGuid.objGUID;
}

bool TeamRankLooterMgr::BetterRankTeam(const RankTeam& rt1, const RankTeam& rt2)
{
	return rt1.totalScore > rt2.totalScore;
}
